﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using Framework;

namespace SchemaMonitor.json
{


	public class CNodeRoot : CNodeBasic
	{
		//Members
		private long _id1;
		private long? _id2;
		private Guid _guid;

		//Properties
		public long Id1 { get { return _id1; } }
		public long Id2 { get { return HasId2 ? _id2.Value : long.MinValue; } }
		private bool HasId2 { get { return _id2.HasValue; } }
		public string IdBase64
		{
			get
			{
				if (!HasId)
					return string.Empty;
				Guid g = this.Guid;
				if (!HasId2)
					return CBinary.ToBase64(BitConverter.GetBytes(Id1));
				return CBinary.ToBase64(_guid.ToByteArray());
			}
			set
			{
				var s = value;
				if (s.Length <= 11)
				{
					while (s.Length < 11)
						s = string.Concat(s, " ");
					var b = CBinary.FromBase64(s);
					_id1 = BitConverter.ToInt64(b, 0);
					_id2 = null;
					_guid = Pad(_id1);
				}
				else if (s.Length <= 24)
				{
					var b = CBinary.FromBase64(s);
					_id1 = BitConverter.ToInt64(b, 0);
					_id2 = BitConverter.ToInt64(b, 8);
					_guid = Join(_id1, _id2.Value);
				}
				else
				{
					this.Guid = CBinary.Sha128_(CBinary.StringToBytes(s));
				}
			}
		}

		//Utilities
		public static Guid Pad(long id1) { return Pad(BitConverter.GetBytes(id1)); }
		private static Guid Pad(byte[] id1)
		{
			var bb = new List<byte>(16);
			bb.AddRange(id1);
			while (bb.Count < 16)
				bb.Add((byte)0);
			return new System.Guid(bb.ToArray());
		}
		private static Guid Join(long id1, long id2)
		{
			var b1 = BitConverter.GetBytes(id1);
			var b2 = BitConverter.GetBytes(id2);
			var bb = new List<byte>(b1.Length + b2.Length);
			bb.AddRange(b1);
			bb.AddRange(b2);
			return new Guid(bb.ToArray());
		}


		//Constructor
		public CNodeRoot(CType t) : base(null, null, t) { }
        public CNodeRoot(byte[] bin, CType t) : base(bin, null, t) { }
        public CNodeRoot(Dictionary<string, object> dict, CType t, CUser c) : base(dict, null, t, c) { }



        //Properties
		public string Id
		{
			get { return GetString("id"); }
			set
			{
				_id2 = null;
				_id1 = long.MinValue;
				_guid = Guid.Empty;
				Set("id", value, null);
			}
		}
		public bool HasId { get { return null != Id; } }
        public static string GuidToStr(Guid g)
        {
            var bb = g.ToByteArray();
            long id1;
            long? id2;
            return GuidToStr(g, out id1, out id2);
        }
        private static string GuidToStr(Guid g, out long id1, out long? id2)
        {
            var bb = g.ToByteArray();
            id1 = BitConverter.ToInt64(bb, 0);
            id2 = BitConverter.ToInt64(bb, 8);
            return string.Concat(id1, "_", id2);
        }

        public static Guid Str2Guid(string s)
		{
			if (string.IsNullOrEmpty(s))
				return Guid.Empty;

			long id;
			if (s.Contains("_"))
			{
				var ss = s.Split((char)'_');
				long id1 = long.Parse(ss[0]);
				long? id2 = long.Parse(ss[1]);
				return Join(id1, id2.Value);
			}
			else if (long.TryParse(s, out id))
				return Pad(id);
			throw new Exception("Failed to parse: " + s);;
		}
		public Guid Guid
		{
			get
			{
				if (!HasId)
					return Guid.Empty;

				if (System.Guid.Empty == _guid)
				{
					string s = this.Id;

					if (s.Contains("_"))
					{
						var ss = s.Split((char)'_');
						_id1 = long.Parse(ss[0]);
						_id2 = long.Parse(ss[1]);
						_guid = Join(_id1, _id2.Value);
					}
					else if (long.TryParse(s, out _id1))
					{
						_id2 = long.MinValue;
						_guid = Pad(_id1);
					}
				}
				return _guid;
			}		
			set
			{
				_guid = value;
                this.Id = GuidToStr(_guid, out _id1, out _id2);
			}
		}
        public override string Name
        {
            get
            {
                var s = base.Name;
                return s;
            }
        }

		public const string CT = "created_time";
		public const string PHOTO_IDS = "photoIds";
		public const string CONT = "content";
		public const string MSG = "message";
		public bool HasCreatedTime
		{
			get
			{
				if (!Fields.Has(CT))
					return false;
				return DateTime.MinValue != CreatedTime;
			}
		}
		public bool HasPhotoIds
		{
			get
			{
				if (!Fields.Has(PHOTO_IDS))
					return false;
				return PhotoIds.Count > 0;
			}
		}
		public bool HasContent
		{
			get
			{
				if (!Fields.Has(CONT))
					return false;
				return string.IsNullOrEmpty(Content);
			}
		}
		public bool HasMessage
		{
			get
			{
				if (!Fields.Has(MSG))
					return false;
				return string.IsNullOrEmpty(Message);
			}
		}
		public DateTime CreatedTime
		{
			get
			{
				if (!Fields.Has(CT))
					return DateTime.MinValue;
				return html.CHtml.ToDate(GetString(CT));
			}
		}
		public string ContentMessageOrName
		{
			get
			{
				var s = Content;
				if (string.IsNullOrEmpty(s))
					s = Message;
				if (string.IsNullOrEmpty(s))
					s = Name;
				return s;
			}
		}
		public string Content
		{
			get
			{
				if (!Fields.Has(CONT))
					return string.Empty;
				return GetString(CT);
			}
		}
		public string Message
		{
			get
			{
				if (!Fields.Has(MSG))
					return string.Empty;
				return GetString(MSG);
			}
		}

		public List<string> Tags
		{
			get
			{
				var tags = new List<string>() { "id" };
				foreach (var j in this.Fields)
					if (!tags.Contains(j.Tag.TagName))
						tags.Add(j.Tag.TagName);
				return tags;
			}
		}

		public List<long> PhotoIds
		{
			get
			{
				if (!Fields.Has(PHOTO_IDS))
					return null;
				return Fields[PHOTO_IDS].AsLongList;
			}
			set
			{
				if (null == value)
				{
					if (Fields.Has(PHOTO_IDS))
						Fields.Remove(Fields[PHOTO_IDS]);
					return;
				}

				if (Fields.Has(PHOTO_IDS))
					Fields[PHOTO_IDS].AsLongList = value;
				else
				{
					var set = new System.Collections.ArrayList(value);
					var f = SchemaMonitor.json.CField.Factory("photoIds", set.ToArray(), this, null);
					this.Fields.Add(f);
				}
			}
		}

		public CNode Persist(CUser u)
		{
			return PersistJson(this, u);
		}


		//Helper
		public static bool HasId_(Dictionary<string, object> dict)
		{
			return null != GetIdAsString(dict);
		}

		public static string GetIdAsString(Dictionary<string, object> dict)
        {
            //Has id key?
            object obj = null;
            if (!dict.TryGetValue("id", out obj))
                return null;
            return obj?.ToString();
        }

        //Static
        public static CNode PersistJson(string typeName, string apiPath, Dictionary<string, object> json, CUser c)
        {
            var type = CType.Cache.GetOrCreate_Manual(typeName, apiPath, c.UserSchemaId);

            return PersistJson(type, json, c);
        }
		public static CNode PersistJson(CType type, Dictionary<string, object> json, CUser c)
		{
			var root = new CNodeRoot(json, type, c);
			return PersistJson(root, c);
		}
		public static CNode PersistJson(CNodeRoot root, CUser c)
		{
			//Create the OO model, add to list
            c.Inserts.Add(root);

            //Check if its known (preloaded or cached)
            var matching = c.Nodes.GetById(root.Guid);

			//Check database, cache result
			if (null == matching)
			{
				matching = new CNode().GetById(root.Guid, true);
				if (null != matching)
					c.Nodes.Add(matching);
			}

			if (null != matching)
            {
				if (matching.NodeHashSha != root.Hash())
					matching.Root.Fields.Merge(root.Fields, c);
				if (matching.HasChanged)
					matching.Save();
				return matching;
            }

            //Save new record, cache result (recursive)
            var n = new CNode(root, c.UserId);
            n.Save();
            c.All.Add(n);
			c.All.Sort();
            return n;
        }
    }
}
